﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Helpers;
using Utils;

namespace swagger2apidocv1
{
    /**
    * @api {get} /user/:id Request User information
    * @apiGroup User
    *
    * @apiParam {Number} id Users unique ID.
    *
    * @apiSuccess {String} firstname Firstname of the User.
    * @apiSuccess {String} lastname  Lastname of the User.
    */
    public class ApiDoc
    {
        public ApiDocOption opts = new ApiDocOption();
        SwaggerInfo _doc;
        string _folder;
        string _outputFolder;
        public ApiDoc(string folder, string outputDir)
        {
            _folder = folder;
            _outputFolder = outputDir;
        }
        public void AddDocInfo(SwaggerInfo info)
        {
            _doc = info;
            if (info.definitions != null)
            {
                _definitions = info.definitions.CastAnyTo<Dictionary<string, DefinitionInfo>>();
            }
        }
        public void Run()
        {
            bool flag = true;
            var info = GetVersion(_doc.info);
            var docsPath = Path.Combine(_folder, _outputFolder);
            EnsureDirectory(docsPath);
            initApiJson(docsPath);
            const string textWrap = "/**<-- text -->*/\n";

            var apiText = "";
            var AllApidocText = "";
            KeyValuePair<string, ParameterInfo> sd = new KeyValuePair<string, ParameterInfo>(null, null);
            if (_doc.securityDefinitions != null)
                sd = _doc.securityDefinitions.FirstOrDefault();
            var security = new ParameterInfo();
            if (sd.Key != null)
            {
                security.name = sd.Value.name;
                security.description = sd.Value.description;
                security.type = sd.Value.type;
                security.@in = sd.Value.@in;
            }
            int index = 0;
            foreach (var apiUrlObj in _doc.paths)
            {
                var apiUrlObjKey = apiUrlObj.Key;
                foreach (var path in apiUrlObj.Value)
                {
                    var itemKey = path.Key;
                    apiText = string.Format(@"
        *@api {0} {1} {2}
                    *@apiVersion {3}
      ", itemKey, apiUrlObjKey, path.Value.summary ?? apiUrlObjKey, info.version);
                    apiText += AddJwt(path.Value.security, security);
                    apiText += AddApiGroup(path.Value.tags);
                    apiText += AddApiParam(path.Value.parameters);
                    apiText += AddApiResponse(path.Value.responses);
                }
                ++index;
                AllApidocText += textWrap.Replace("<-- text -->", apiText);
                if (index > 0 && (index % opts.cutting) == 0)
                {
                    AllApidocText += "<-- end -->\n";
                }
            }
            var cuttingApiTexts = AllApidocText.Split("< --end-- >");
            int i = 0;
            foreach (var cuttingText in cuttingApiTexts)
            {
                if (!string.IsNullOrEmpty(cuttingText) && cuttingText != "\n")
                {
                    File.WriteAllText($"{docsPath}/{(i++)}.js", cuttingText, Encoding.UTF8);
                    //fs.writeFile(`${ docsPath}/${ index}.js`, cuttingText, 'utf-8', (err) => {
                    //    if (err)
                    //        return err;
                    //});
                }
            }
            if (flag && opts.apidoc)
            {
                flag = false;
                var outPath = $"{ opts.apidocPath}";
                EnsureDirectory(outPath);
                ShellManager.ExecuteCmd(@"apidoc", $"-i { docsPath} -o { outPath}", null, true, false, (s, e) =>
                    {
                        Console.WriteLine("apidoc excute exit.");
                    }, (s, e) =>
                 {
                     Console.WriteLine($"apidoc data:{e.Data}");
                 }, (s, e) =>
              {
                  Console.WriteLine($"apidoc error:{e.Data}");
              });
            }
        }
        void initApiJson(string rootDir)
        {
            var info = _doc.info;
            var apidocBaseData = new
            {
                name = info.title ?? "标题",
                version = info.version ?? "0.0.1",
                description = info.description ?? "",
                title = this.opts.title ?? info.title ?? "",
                url = this.opts.baseUrl ?? "",
            };
            File.WriteAllText($"{rootDir}/apidoc.json", apidocBaseData.ToJson(), new UTF8Encoding(false));
        }
        void EnsureDirectory(string dir)
        {
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
        }
        Regex _versionRegex = new Regex(@"\..*\..*", RegexOptions.Compiled);
        ApiDocInfo GetVersion(ApiDocInfo info)
        {
            var version = info.version;
            var newinfo = info.CastAnyTo<ApiDocInfo>();
            var match = _versionRegex.Match(version);
            if (!match.Success)
                newinfo.version = version + ".0";
            return newinfo;
        }
        void GenerateApi2Doc()
        {
            var sb = new StringBuilder();
            foreach (var item in _doc.paths)
            {
                var apiUrlObj = item.Key;
                var itemApidData = item.Value;
                foreach (var methodItem in itemApidData)
                {
                    var summary = apiUrlObj;
                    if (!string.IsNullOrEmpty(itemApidData[methodItem.Key].summary))
                        summary = itemApidData[methodItem.Key].summary;
                    var apiText = $"\n*@api {{{methodItem.Key}}} {apiUrlObj} {summary}\n* @apiVersion ${_doc.info.version}\n";
                }
                //sb.Append($"/**{apiItemText}*/\n");
            }
        }
        string AddJwt(Dictionary<string, ParameterInfo> securityArr, ParameterInfo bearer)
        {
            var newText = "";
            var jwtDes = bearer?.description ?? "登录接口返回的jwt";//登录、注册接口返回的jwt
            var jwtName = bearer.name ?? "Authorization";
            // 带有指定bearer字段才添加jwt信息
            var addFlag = securityArr != null && (securityArr.ContainsKey("bearer") || securityArr.ContainsKey("Bearer"));

            if (addFlag)
            {
                newText += $"*@apiHeader {{String}} {jwtName} { jwtDes}\n";
            }
            return newText;
        }
        string AddApiGroup(string[] tags)
        {
            var newText = "";
            if (tags != null)
            {
                Array.ForEach<string>(tags, tagItem =>
                {
                    newText += $"*@apiGroup { tagItem}\n";
                });
            }
            else
            //if (tags.Length == 0)
            {
                newText = "*@apiGroup default\n";
            }
            return newText;
        }
        string AddApiParam(List<dynamic> parameters)
        {
            var newText = "";
            if (parameters != null)
            {
                foreach (var item in parameters)
                {
                    //{ { "in": "path",   "name": "id",   "description": "设备编号",   "required": true,   "type": "string",   "format": "string" } }
                    //required
                    bool required = item.required;
                    dynamic schema = item.schema;
                    string description, type, name;
                    description = item.description;
                    type = item.type;
                    //schema = item.@in;
                    name = item.name;
                    //let { name } = item;
                    //initData.treeLevelIndex = 1;
                    name = required ? name : $"[{ name}]";
                    newText += string.Format("*@apiParam {0} {1} {2}\n", type, name, description ?? "");
                    if (schema != null)
                    {
                        newText = this.HandleSchema("apiParam", schema, null, null);
                    }
                }
            }
            return newText;
        }
        string AddApiResponse(Dictionary<string, dynamic> responses)
        {
            var newText = "";
            if (responses != null)
            {
                foreach (var item in responses)
                {
                    //                  {
                    //                      {
                    //                          "description": "设备列表",
                    //"schema": {
                    //                              "type": "array",
                    //  "items": {
                    //                                  "$ref": "#/definitions/DeviesInfo"
                    //  }
                    //                          }
                    //                      }
                    //                  }

                    //}
                    dynamic val = item.Value;
                    dynamic schema = val.schema;
                    string type, name, description;
                    type = val.type;
                    name = val.name;
                    description = val.description;
                    //type = name = description = "";
                    //const { type, name, description, schema } = responses[item];
                    //initData.treeLevelIndex = 1;
                    bool isSchema = false;
                    if (schema != null)
                    {
                        if (schema["$ref"] != null)
                            isSchema = true;
                    }
                    if(isSchema)
                        newText = this.HandleSchema($"apiSuccess(Success { item})", schema, null, null);
                    else
                    {
                        newText += string.Format("* @apiSuccess(Success {0}) {{{1}}} {2} {3}\n", item, type ?? "string", name ?? "success", description ?? "");
                    }
                };//);
            }
            return newText;
        }
        string HandleSchema(string apidocName, dynamic schema, string oldMergeDefName, string oldDes)
        {
            //const { schema, oldMergeDefName, oldDes
            //    } = opts;
            //const { type, items, $ref } = schema;
            var mergeApiParam = "";
            var defName = "";
            if (schema.type == "array")
            {
                foreach (var v in schema.items)
                {
                    defName = MatchRefName($"{v}");
                    mergeApiParam += HandleSchemaDef(apidocName, defName, oldMergeDefName, oldDes);
                }
                //    Object.values(items).forEach((v) => {
                //    defName = this.matchRefName(v);
                //    mergeApiParam += this.handleSchemaDef(apidocName, {
                //        defName, oldMergeDefName, oldDes,
                //                    });
                //});
            }
            else
            {
                defName = this.MatchRefName(schema["$ref"].ToString());
                mergeApiParam = this.HandleSchemaDef(apidocName, defName, oldMergeDefName, oldDes);
            }
            return mergeApiParam;
        }
        Regex _definitionsRegex = new Regex(@"(#\/definitions\/)(?<name>.*)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        string MatchRefName(string data)
        {
            try
            {
                var mtch = _definitionsRegex.Match(data);
                if (mtch.Success && mtch.Groups["name"].Success)
                    return mtch.Groups["name"].Value;
            }
            catch (Exception)
            {
            }
            return null;
        }
        public class DefinitionInfo
        {
            public string type { get; set; }
            public Dictionary<string, dynamic> properties { get; set; }
            public List<string> required { get; set; }
        }
        Dictionary<string, DefinitionInfo> _definitions = new Dictionary<string, DefinitionInfo>();
        string HandleSchemaDef(string apidocName, string defName, string oldMergeDefName, string oldDes)
        {
            var mergeApiParam = "";
            //    const { defName, oldMergeDefName, oldDes
            //} = opts;
            //string defName = schema.defName;
            try
            {
                if (defName == null)
                    return mergeApiParam;
                var type = _definitions[defName].type;
                var properties = _definitions[defName].properties;
                var required = _definitions[defName].required;
                //            const { type, properties, required
                //} = this.definitions[defName];
                var fieldReqired = new Dictionary<string, bool>();
                if (required != null)
                {
                    foreach (var item in required)
                    {
                        fieldReqired[item] = true;
                    }
                }
                if (properties != null)
                {
                    foreach (var key in properties.Keys)
                    {
                        var mergeName = key;
                        if (!string.IsNullOrEmpty(oldMergeDefName))
                        {
                            mergeName = $"{ oldMergeDefName}.{ key}";
                        }
                        mergeApiParam += SchemaDefProperties(apidocName, key, mergeName, properties, fieldReqired);
                    }
                }
            }
            catch (Exception ex)
            {

            }
            return mergeApiParam;
        }
        string SchemaDefProperties(string apidocName, string key, string mergeName, Dictionary<string, dynamic> properties, Dictionary<string, bool> fieldReqired)
        {

            //        const { key, mergeName, properties, fieldReqired
            //} = opts;
            var data = properties[key];
            var reqiredName = mergeName;
            var newType = data.type;
            var mergeApiParam = "";
            if (data != null && !string.IsNullOrEmpty($"{data.description}") && data.allOf != null)
            {
                newType = this._definitions[this.GetDataAllOfParamter(data, "refName")].type;
                data.description = this.GetDataAllOfParamter(data, "des");
            }
            var feildName = (fieldReqired.ContainsKey(key) && fieldReqired[key]) ? mergeName : reqiredName;
            mergeApiParam += string.Format(@"* @{0}{{{1}}} {2} {3}\n", apidocName, newType, feildName, data.description ?? "");
            if (data.title != null)
            {
                var defName = this.MatchRefName(data.allOf[0]["$ref"]);
                newType = this.GetMapingType(defName);
                //if (initData.treeLevelIndex < this.opts.treeLevel)
                //{
                //    initData.treeLevelIndex += 1;
                //    mergeApiParam += this.handleSchema(apidocName, {
                //    schema: this.getDataAllOfParamter(data),
                //                    oldMergeDefName: mergeName,
                //                    oldDes: this.getDataAllOfParamter(data, 'des'),
                //                });
                //}
            }
            else
            {
                //集合类型处理，例如 array[string number]
                if (data.items != null)
                {
                    var defName = MatchRefName($"{data.items}");
                    if (!string.IsNullOrEmpty(defName))
                    {
                        newType = $"{newType}[{GetMapingType(defName)}]";
                    }
                    else
                    {
                        Dictionary<string, object> items = Helpers.ObjectEx.CastAnyTo<Dictionary<string, object>>(data.items);

                        var lst = new HashSet<string>();

                        GetTypeArray(items, ref lst);
                        var text = string.Join(' ', lst);
                        newType = $"{newType}[{text}]";
                    }
                }
                mergeApiParam += string.Format(@"* @{0}{{{1}}} {2} {3}\n", apidocName, newType, feildName, data.description ?? "");
            }
            return mergeApiParam;
        }
        void GetTypeArray(Dictionary<string, object> items, ref HashSet<string> lst)
        {
            var mappingName = "";
            foreach (var typeName in items.Values)
            {
                if (typeName is string)
                {
                    var type = $"{typeName}";
                    mappingName = this.MatchRefName(type);
                    if (!string.IsNullOrEmpty(mappingName))
                    {
                        lst.Add(this.GetMapingType(mappingName));
                    }
                    else
                        lst.Add(type);
                }
                else
                {
                    var t = Helpers.ObjectEx.CastAnyTo<Dictionary<string, object>>(typeName);
                    GetTypeArray(t, ref lst);
                }
            }
        }
        string GetDataAllOfParamter(dynamic data, string field = "refDom")
        {
            try
            {
                //var typeData = {
                //    refDom: data.allOf.filter(item => item.$ref)[0],
                //    refName: this.matchRefName(data.allOf.filter(item => item.$ref)[0].$ref),
                //    des: data.allOf.filter(item => item.description)[0].description,
                //};
                //return typeData[field];
            }
            catch (Exception)
            {
            }
            return null;
        }
        string GetMapingType(string name)
        {
            try
            {
                return _definitions[name].type;
            }
            catch (Exception)
            {
                return null;
            }
        }
        public void Excute()
        {
            Console.WriteLine(Utils.ShellManager.RunCmd($"apidoc -i {_folder} -o {_outputFolder}",
                null, true, false, null, null, null));
        }
    }

}